﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Reflection;

namespace Faml.Frontend
{
    public class FamlFunctionCallExpression : FamlExpression
    {
        /// <summary>Function name</summary>
        private string func;
        /// <summary>Operation to call</summary>
        private Operation operation = null;
        /// <summary>Function arguments</summary>
        private IList<FamlExpression> args;
        /// <summary>Library to look function definitions</summary>
        private OperationLibrary library;

        public FamlFunctionCallExpression(string func, List<FamlExpression> args, OperationLibrary library)
        {
            this.func = func;
            this.args = args;
            this.library = library;
            MakeMetadata();
        }
       

        public FamlFunctionCallExpression(Operation operation, params FamlExpression[] args)
            : this(operation, (IList<FamlExpression>)args)
        {
        }

        //public FamlFunctionCallExpression(MethodInfo method, IList<FamlExpression> args)
        //{
        //    this.method = method;

        //    object[] attrs = method.GetCustomAttributes(typeof(OperationNameAttribute), false);
        //    if (attrs != null && attrs.Length > 0)
        //    {
        //        OperationNameAttribute att = (OperationNameAttribute)attrs[0];
        //        this.func = att.Name;
        //    }
        //    else
        //    {
        //        this.func = method.Name;
        //    }

        //    this.args = args;
        //    this.library = null;
        //    MakeMetadata();
        //}

        public FamlFunctionCallExpression(Operation operation, IList<FamlExpression> args)
        {
            this.operation = operation;
            this.func = operation.Name;            

            this.args = args;
            this.library = null;
            MakeMetadata();
        }

        public FamlFunctionCallExpression(OperationLibrary library, string func, params FamlExpression[] args)
        {
            this.func = func;
            this.library = library; 
            this.args = new List<FamlExpression>();
            foreach (FamlExpression arg in args)
                this.args.Add(arg);
            MakeMetadata();
        }

        public string Function
        {
            get
            {
                return func;
            }
        }

        public IList<FamlExpression> Arguments
        {
            get
            {
                return args;
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(func);
            sb.Append("(");
            for (int i = 0; i < args.Count; i++)
            {
                sb.Append(args[i].ToString());
                if (i < args.Count - 1)
                    sb.Append(", ");
                else
                    sb.Append(")");
            }
            return sb.ToString();
        }

        public override FamlExpression Expand()
        {
            List<FamlExpression> newArgs = new List<FamlExpression>(args.Count);
            foreach (FamlExpression arg in args)
                newArgs.Add(arg.Expand());
            return new FamlFunctionCallExpression(func, newArgs, library);
        }

        protected override Type ComputeType()
        {
            return Operation.Method.ReturnType;
        }

        public Operation Operation
        {
            get
            {
                if (operation == null)
                {
                    Type[] argTypes = new Type[args.Count];
                    for(int i = 0;i<args.Count;i++)
                        argTypes[i] = args[i].Type;

                    operation = library.GetOperation(func, argTypes);
                }
                return operation;
            }
        }

        protected override void MakeMetadata()
        {
            int height = 1;
            int hash = NodeType.GetHashCode() ^ func.GetHashCode();

            foreach (FamlExpression arg in args)
            {
                hash ^= arg.Metadata.Hash;
                height = Math.Max(height, arg.Metadata.Height + 1);
                this.metadata.AddIfNotExist(arg.NodeParameters);
            }
            
            this.metadata.Height = height;
            this.metadata.Hash = hash;
        }

        public override FamlExpressionType NodeType
        {
            get { return FamlExpressionType.FunctionCall; }
        }

        public override IEnumerable<FamlExpression> Children
        {
            get
            {
                foreach (var item in this.args)
                {
                    yield return item;
                }
            }
        }
    }
}